// Copyright (c) 2023 Presto Labs Pte. Ltd.
// Author: daniel

#pragma once

#include <chrono>
#include <utility>

#include <boost/asio/spawn.hpp>
#include <boost/asio/steady_timer.hpp>
#include <pybind11/pybind11.h>

#include "appcoin2/support/forwarding_strategy.h"

namespace coin2::exchange::driver {

namespace impl {

namespace py = pybind11;

class ProtoForwarder : public coin2::appcoin2::support::ForwardingStrategy<ProtoForwarder> {
  friend class coin2::appcoin2::support::ForwardingStrategy<ProtoForwarder>;
  using Base = coin2::appcoin2::support::ForwardingStrategy<ProtoForwarder>;

 public:
  explicit ProtoForwarder(py::object pipe_incoming, py::object pipe_outgoing)
      : pipe_incoming_{pipe_incoming}, pipe_outgoing_{pipe_outgoing} {
    CHECK(py::hasattr(pipe_incoming_, "recv_bytes"));
    CHECK(py::hasattr(pipe_outgoing_, "send_bytes"));
  }

 protected:
  void Start(::coin2::app::Driver* driver) {
    running_ = true;
    // periodic task
    driver->PostPeriodicTask(
        std::bind(&Base::Push, this),
        50'000'000LL /* period */,
        10'000'000LL /* clock */);

    // poll message - TODO(daniel): can make it async/epoll
    auto* ioc = driver->live_executor()->MainIoc();
    boost::asio::spawn(*ioc, [ioc, this](boost::asio::yield_context yield) {
      boost::asio::steady_timer timer(*ioc);
      boost::system::error_code ec;
      while (true) {
        Poll();

        timer.expires_after(boost::asio::chrono::microseconds(10));
        timer.async_wait(yield[ec]);
      }
    });
  }

  void Stop() { running_ = false; }

 private:
  template <class T>
  bool DoSend(T&& msg) {
    if (!running_) {
      return false;
    }
    pipe_outgoing_.attr("send_bytes")(py::bytes(msg));
    return true;
  }

  template <class T>
  bool DoRecv(T* msg) {
    if (!running_) {
      return false;
    }
    auto ready = pipe_incoming_.attr("poll")();
    if (py::str(ready).is(py::str(Py_False))) {
      return false;
    }
    py::bytes buf = pipe_incoming_.attr("recv_bytes")();
    *msg = std::move(buf);
    return true;
  }

 private:
  py::object pipe_incoming_;
  py::object pipe_outgoing_;
  bool running_ = false;
};  // namespace impl

}  // namespace impl

using impl::ProtoForwarder;
}  // namespace coin2::exchange::driver
